﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using LoungeRepo.Contracts.Normalizer;
using LoungeRepo.Normalizer;
using LoungeRepo.Normalizer.DataStructures;
using NUnit.Framework;

namespace Test.LoungeRepo.Normalizer
{
    [TestFixture]
    public class testObjectGraphNormalizer
    {
        [Test]
        public void Normalize_single_object()
        {
            var sut = new ObjectGraphNormalizer();

            var o = new Simple {Text = "hello, world" };
            var no = sut.NormalizeNonEntity(o);

            Assert.AreEqual(1, no.Fields.Count());
            Assert.AreEqual("Text", no.Fields.First().Name);
            Assert.AreEqual("hello, world", no.Fields.First().Value);
        }


        [Test]
        public void Process_null_references()
        {
            var sut = new ObjectGraphNormalizer();

            var o = new Simple { Text = null };
            var no = sut.NormalizeNonEntity(o);

            Assert.IsNull(no.Fields.First().Value);
        }


        [Test]
        public void Ignore_transient_fields()
        {
            var sut = new ObjectGraphNormalizer();

            var no = sut.NormalizeNonEntity(new Node
                        {
                            Text = "hello, world",
                            Comment = "ignore this"
                        });

            Assert.AreEqual(2, no.Fields.Count());

            Assert.AreEqual("Text", no.Fields.ElementAt(0).Name);
            Assert.AreEqual("Child", no.Fields.ElementAt(1).Name);
        }


        [Test]
        public void Normalize_object_graph()
        {
            var sut = new ObjectGraphNormalizer();

            var o = new Node{Text = "parent"};
            var oChild = new Node {Text = "child"};
            o.Child = oChild;

            var no = sut.NormalizeNonEntity(o);

            INormalizedObject noChild = (INormalizedObject)no.Fields.ElementAt(1).Value;
            Assert.AreEqual("child", noChild.Fields.First().Value);
        }

        
        [Test]
        public void Normalize_cyclic_graph()
        {
            var sut = new ObjectGraphNormalizer();

            var o = new Node {Text = "parent"};
            var oChild = o;
            o.Child = oChild; // cyclic reference

            NormalizedObject no = (NormalizedObject)sut.NormalizeNonEntity(o);

            NormalizedObjectReference noChild = (NormalizedObjectReference)no.Fields.ElementAt(1).Value;
            Assert.AreEqual(no.RefId, noChild.RefId);
        }


        [Test]
        public void Normalize_multiple_refs_to_same_entity()
        {
            EntityNode e = new EntityNode {Id = "1", Partition = ""};
            var o = new ObjectReferencingEntities();
            o.E1 = e;
            o.E2 = e;

            var sut = new ObjectGraphNormalizer();
            var no = sut.NormalizeNonEntity(o);

            Assert.IsInstanceOfType(typeof(NormalizedEntityReference),  no.Fields.Single(f => f.Name == "E1").Value);
            Assert.IsInstanceOfType(typeof(NormalizedEntityReference), no.Fields.Single(f => f.Name == "E2").Value);
        }


        [Test]
        public void Normalize_multiple_refs_to_same_array()
        {
            EntityNode e = new EntityNode { Id = "1", Partition = "" };
            var o = new ObjectReferencingArrays();
            o.Entities0 = new[] {e};
            o.Entities1 = o.Entities0;

            var sut = new ObjectGraphNormalizer();
            var no = sut.NormalizeNonEntity(o);

            Assert.AreEqual("Entities0", no.Fields.ElementAt(0).Name);
            Assert.IsInstanceOfType(typeof(NormalizedObject), no.Fields.ElementAt(0).Value);
            Assert.AreEqual("Entities1", no.Fields.ElementAt(1).Name);
            Assert.IsInstanceOfType(typeof(NormalizedObjectReference), no.Fields.ElementAt(1).Value);
        }


        [Test]
        public void Process_scalar_types()
        {
            var sut = new ObjectGraphNormalizer();

            var o = new Scalars
                        {
                            _bool = true,
                            _byte = 255,
                            _char = 'c',
                            _decimal = 314.15m,
                            _double = 3.1415,
                            _float = 2.71828f,
                            _int = -31415,
                            _long = -314159265,
                            _sbyte = -127,
                            _short = 314,
                            _string = "hello, world",
                            _uint = 3141592,
                            _ulong = 314159265,
                            _ushort = 31415
                        };
            var no = sut.NormalizeNonEntity(o);

            Assert.AreEqual(true, GetFieldValueByName(no, "_bool"));
            Assert.AreEqual(255, GetFieldValueByName(no, "_byte"));
            Assert.AreEqual('c', GetFieldValueByName(no, "_char"));
            Assert.AreEqual(314.15m, GetFieldValueByName(no, "_decimal"));
            Assert.AreEqual(3.1415, GetFieldValueByName(no, "_double"));
            Assert.AreEqual(2.71828f, GetFieldValueByName(no, "_float"));
            Assert.AreEqual(-31415, GetFieldValueByName(no, "_int"));
            Assert.AreEqual(-314159265, GetFieldValueByName(no, "_long"));
            Assert.AreEqual(-127, GetFieldValueByName(no, "_sbyte"));
            Assert.AreEqual(314, GetFieldValueByName(no, "_short"));
            Assert.AreEqual("hello, world", GetFieldValueByName(no, "_string"));
            Assert.AreEqual(3141592, GetFieldValueByName(no, "_uint"));
            Assert.AreEqual(314159265, GetFieldValueByName(no, "_ulong"));
            Assert.AreEqual(31415, GetFieldValueByName(no, "_ushort"));
        }


        [Test]
        public void Process_special_types()
        {
            var sut = new ObjectGraphNormalizer();

            var o = new SpecialSimpleTypes{ dt = new DateTime(2009, 08, 23), e=BindingFlags.Instance | BindingFlags.Public };
            var no = sut.NormalizeNonEntity(o);

            Assert.AreEqual(new DateTime(2009, 8, 23), GetFieldValueByName(no, "dt"));
            Assert.AreEqual(BindingFlags.Instance | BindingFlags.Public, GetFieldValueByName(no, "e"));
        }


        [Test]
        public void Normalize_single_dim_array()
        {
            string[] array = { "a", "b", "c" };

            var sut = new ObjectGraphNormalizer();
            var no = sut.NormalizeArray(array);

            Assert.AreEqual(array.GetType(), no.ObjectType);
            Assert.AreEqual("_arrayRank", no.Fields.First().Name);
            Assert.AreEqual(1, no.Fields.First().Value);
            Assert.AreEqual(3, no.Fields.ElementAt(1).Value);
            Assert.AreEqual(5, no.Fields.Count());
        }


        [Test]
        public void Normalize_rectangular_array()
        {
            int[,] array = new int[,] { { 1, 11 }, { 2, 22 }, { 3, 33 } };

            var sut = new ObjectGraphNormalizer();
            var no = sut.NormalizeArray(array);

            Assert.AreEqual(array.GetType(), no.ObjectType);
            Assert.AreEqual("_arrayRank", no.Fields.First().Name);
            Assert.AreEqual(2, no.Fields.First().Value);
            Assert.AreEqual(3, no.Fields.ElementAt(1).Value);
            Assert.AreEqual(2, no.Fields.ElementAt(2).Value);
            Assert.AreEqual(9, no.Fields.Count());
        }


        [Test]
        public void Normalize_jagged_arrays()
        {
            int[][] array = new int[2][];
            array[0] = new int[] {1};
            array[1] = new int[] { 2, 22 };

            var sut = new ObjectGraphNormalizer();
            var no = sut.NormalizeArray(array);

            Assert.AreEqual(array.GetType(), no.ObjectType);
            Assert.AreEqual("_arrayRank", no.Fields.First().Name);
            Assert.AreEqual(1, no.Fields.First().Value);
            Assert.AreEqual(4, no.Fields.Count());

            Assert.IsInstanceOfType(typeof (int[]), no.Fields.Last().Value);
        }


        [Test]
        public void Normalize_arrys_depending_on_element_type()
        {
            var sut = new ObjectGraphNormalizer();

            // simple type arrays are kept as is
            int[] intArray = new int[] {1};
            Assert.IsInstanceOfType(typeof (int[]), sut.NormalizeFieldValue(intArray));

            // complex type arrays are transformed into normalized objects
            Uri[] uriArray = new Uri[] {new Uri("http://www.microsoft.com")};
            var no = (NormalizedObject)sut.NormalizeFieldValue(uriArray);
            Assert.AreEqual(uriArray.GetType(), no.ObjectType);
            Assert.AreEqual(3, no.Fields.Count());
            Assert.AreEqual("_arrayRank", no.Fields.First().Name);
        }

        
        [Test]
        public void Normalize_struct_as_complex_object()
        {
            SimpleStruct s = new SimpleStruct {I = 99, S = "hello, world"};

            var sut = new ObjectGraphNormalizer();

            var no = (NormalizedObject)sut.NormalizeFieldValue(s);
            Assert.AreEqual(2, no.Fields.Count());
            Assert.AreEqual(99, no.Fields.First().Value);
            Assert.AreEqual("hello, world", no.Fields.Last().Value);
        }


        [Test]
        public void Report_entities_as_they_are_normalized()
        {
            List<object> normalizedEntities = new List<object>();
            var sut = new ObjectGraphNormalizer((e, eid) => normalizedEntities.Add(e));

            var child = new EntityNode {Id = "2"};
            var parent = new EntityNode { Id = "1", Next = child };

            sut.NormalizeEntity(parent, parent);

            Assert.AreEqual(2, normalizedEntities.Count);
            Assert.AreSame(parent, normalizedEntities[0]);
            Assert.AreSame(child, normalizedEntities[1]);
        }


        private object GetFieldValueByName(INormalizedObject source, string fieldname)
        {
            return source.Fields.Single(f => f.Name == fieldname).Value;
        }
    }
}
